﻿using System.Collections;
using System.IO;
using System.Reflection;
using Unity.EditorCoroutines.Editor;
using UnityEditor;
using UnityEngine;
using Yoozoo.Core.Extensions.CSharp;

namespace Yoozoo.Art
{
    [CustomEditor(typeof(BatchSetLodGroup))]
    public class BatchSetLodGroupEditor : UnityEditor.Editor
    {
        private BatchSetLodGroup _target;
        private static MethodInfo _calculateWorldReferencePointMethodInfo;
        private static MethodInfo _calculateVisualizationDataMethodInfo;
        private static FieldInfo _activeRelativeScreenSizeFieldInfo;

        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
            _target = target as BatchSetLodGroup;
            if (_target == null)
                return;

            if (string.IsNullOrEmpty(_target.Path))
            {
                EditorGUILayout.HelpBox("预制体路径没有设置", MessageType.Warning);
                return;
            }


            if (!Directory.Exists(_target.Path))
            {
                EditorGUILayout.HelpBox("预制体路径不存在", MessageType.Warning);
                return;
            }

            if (_target.Camera == null)
            {
                EditorGUILayout.HelpBox("摄像机没有设置", MessageType.Warning);
                return;
            }


            if (GUILayout.Button("设置LOD Group"))
            {
                QualitySettings.lodBias = _target.LodBias;

                Selection.activeObject = _target.Camera;
                EditorApplication.ExecuteMenuItem("GameObject/Align View to Selected");

                var guids = AssetDatabase.FindAssets("t:Prefab", new[] { _target.Path });
                EditorCoroutineUtility.StartCoroutine(CheckPrefabs(guids), this);
            }
        }

        private IEnumerator CheckPrefabs(string[] guids)
        {
            var progress = new GameObject();

            for (var i = 0; i < guids.Length; i++)
            {
                var guid = guids[i];
                string assetPath = AssetDatabase.GUIDToAssetPath(guid);

                if (!string.IsNullOrEmpty(_target.TestAssetPath))
                    assetPath = _target.TestAssetPath;

                progress.name = $"[{i + 1}/{guids.Length}]{Path.GetFileName(assetPath)}";

                var asset = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
                if (asset == null) continue;

                var isPrefab = PrefabUtility.GetPrefabAssetType(asset) == PrefabAssetType.Regular;
                if (!isPrefab) continue;

                AddLodGroup(asset);

                var go = PrefabUtility.InstantiatePrefab(asset) as GameObject;
                if (go == null) continue;

                Selection.activeObject = go;

                _target.Renderers = go.GetComponentsInChildren<Renderer>();

                var lodGroup = go.GetComponentInChildren<LODGroup>();
                var lods = lodGroup.GetLODs();

                yield return TrySetLod(go, lods, lodGroup, assetPath);

                //DestroyImmediate(go);
                go.transform.SetParent(progress.transform);

                if (!string.IsNullOrEmpty(_target.TestAssetPath))
                    break;
            }
        }

        private IEnumerator TrySetLod(GameObject go, LOD[] lods, LODGroup lodGroup, string assetPath)
        {
            for (var i = 0; i < _target.Distances.Length; i++)
            {
                float distance = _target.Distances[i];
                go.transform.SetLocalPositionX(-distance);

                int lodIndex = lods.Length - i - 1;
                if (lodIndex < 0) break;

                var lod = lods[lodIndex];
                if (lod.renderers.IsEmpty()) break;

                var renderer = lod.renderers[0];
                float previousLodHeight = 1f - 0.001f;
                if (lodIndex > 0)
                    previousLodHeight = lods[lodIndex - 1].screenRelativeTransitionHeight - 0.001f;

                float nextLodHeight = 0.001f;
                if (lodIndex + 1 < lods.Length)
                    nextLodHeight = lods[lodIndex + 1].screenRelativeTransitionHeight + 0.001f;

                float height = previousLodHeight;
                for (; height >= nextLodHeight; height -= 0.001f)
                {
                    lod.screenRelativeTransitionHeight = height;
                    lods[lodIndex] = lod;
                    lodGroup.SetLODs(lods);
                    yield return null;
                    if (renderer.isVisible)
                        break;
                }

                height = Mathf.Ceil(height * 100) / 100;
                lod.screenRelativeTransitionHeight = height;
                lods[lodIndex] = lod;
                lodGroup.SetLODs(lods);

                PrefabUtility.SaveAsPrefabAssetAndConnect(go, assetPath, InteractionMode.AutomatedAction);

            }
        }

        //private static float CalculateVisualizationData(Camera camera, LODGroup group, int lodLevel)
        //{
        //    if (_calculateVisualizationDataMethodInfo == null)
        //    {
        //        _calculateVisualizationDataMethodInfo = typeof(LODUtility).GetMethod("CalculateVisualizationData",
        //            BindingFlags.NonPublic | BindingFlags.Static);
        //    }

        //    var result = _calculateVisualizationDataMethodInfo?.Invoke(null, new object[] {camera, group, lodLevel});
        //    if (result == null)
        //        return 0.01f;

        //    if (_activeRelativeScreenSizeFieldInfo == null)
        //    {
        //        var editorAssembly = typeof(EditorGUIUtility).Assembly;
        //        _activeRelativeScreenSizeFieldInfo = editorAssembly.GetType("UnityEditor.LODVisualizationInformation")
        //            .GetField("activeRelativeScreenSize");
        //    }

        //    float size = (float)_activeRelativeScreenSizeFieldInfo.GetValue(result);
        //    return size;
        //}

        //private static Vector3 CalculateWorldReferencePoint(LODGroup group)
        //{
        //    if (_calculateWorldReferencePointMethodInfo == null)
        //    {
        //        _calculateWorldReferencePointMethodInfo = typeof(LODUtility).GetMethod("CalculateWorldReferencePoint",
        //            BindingFlags.NonPublic | BindingFlags.Static);
        //    }

        //    var result = _calculateWorldReferencePointMethodInfo?.Invoke(null, new object[] { group });
        //    if (result is Vector3 point)
        //        return point;
            
        //    return Vector3.one;
        //}

        private static void AddLodGroup(GameObject asset)
        {
            var lodGroup = asset.GetComponentInChildren<LODGroup>();
            if (!lodGroup)
            {
                lodGroup = asset.AddComponent<LODGroup>();
                var renderers = asset.GetComponentsInChildren<Renderer>();

                lodGroup.SetLODs(new[]
                {
                    new LOD(0.01f, renderers),
                });
                lodGroup.RecalculateBounds();
            }
        }
    }
}
